0%

方法分派

以下内容完全来源于 RednaxelaFX 两篇文章的总结:

  1. 方法分派(method dispatch)的几个例子
  2. C# 4的方法动态分派逻辑变了……

面向对象语言中经常会对函数调用的第一个参数做特殊处理,包括语法和语义都很特别。

  1. 语法的特别之处在于实际上的第一个参数不用写在参数列表里,而是写在某种特殊符号之前(b.foo(0)的“.”),也就是所谓的隐含参数。
  2. 语义的特别之处在于这第一个参数的称为方法调用的接收者(reciever),它的实际类型会参与到方法分派的判断中,而其余的参数要么只参与静态类型判断(单一分派+方法重载),要么也以实际类型参与到方法分派的判断(多分派)。

在单一分派静态类型的面向对象语言中,重载仍然是编译时概念:编译器只会根据静态变量的类型来判断选择哪个版本的重载,而不像运行时多态那样根据值的实际类型来判断。

在C#4.0之前方法都是单一分派的,C#4.0增加了“动态类型”,因为dynamic类型运行时才能确定类型,所以会参与方法分派。也就是说,如果一个虚方法调用的参数的类型是都是dynamic(如果有静态类型参数,那么静态类型参数将不参与方法分派,而是在编译期参与方法推断:This means that for all arguments not statically typed dynamic, the compile time types will be used, regardless of their runtime types.),那么整个方法调用都无法在编译时判定到底应该选用哪个具体版本。CLR会根据方法接收者(this)和每个参数的实际类型来进行方法分派,这个语义与多分派的语义是相同的。如下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
using System;

namespace MethodDispatchTest
{
public class A
{

}

public class B : A
{

}

public class Foo
{
public virtual void Do(A a, A a1)
{
Console.WriteLine("Foo.Do(A, A)");
}

public virtual void Do(A a, B b)
{
Console.WriteLine("Foo.Do(A, B)");
}

public virtual void Do(B b, A a)
{
Console.WriteLine("Foo.Do(B, A)");
}

public virtual void Do(B b, B b1)
{
Console.WriteLine("Foo.Do(B, B)");
}

public void Doo(A a)
{
Console.WriteLine("Foo.Do(A)");
}
}

public class Goo : Foo
{
public override void Do(A a, A a1)
{
Console.WriteLine("Goo.Do(A, A)");
}

public override void Do(A a, B b)
{
Console.WriteLine("Goo.Do(A, B)");
}

public override void Do(B b, A a)
{
Console.WriteLine("Goo.Do(B, A)");
}

public override void Do(B b, B b1)
{
Console.WriteLine("Goo.Do(B, B)");
}

public new void Doo(A a)
{
Console.WriteLine("Goo.Do(A)");
}
}

class Program
{
static void Main(string[] args)
{
A a = new A();
A b = new B();
Foo goo = new Goo();

dynamic da = a;
dynamic db = b;
dynamic dgoo = goo;

//This means that for all arguments not statically typed dynamic, the compile time types will be used, regardless of their runtime types.
//goo和dynamic参数会参与方法分派,静态类型参与编译器推断,不完全符合多分派
goo.Do(a, b); // Goo.Do(A, A), single-dispatch
goo.Do(da, b); // Goo.Do(A, A), single-dispatch
goo.Do(db, b); // Goo.Do(B, A), single-dispatch
goo.Do(a, db); // Goo.Do(A, B), single-dispatch
goo.Do(b, db); // Goo.Do(A, B), single-dispatch
dgoo.Do(a, b); // Goo.Do(A, A), single-dispatch

//Do是虚方法,da、db都是dynamic的,所以是multi-dispatch
goo.Do(da, db); // Goo.Do(A, B)

//Doo不是虚方法,goo不参与方法分派, 编译器静态确定方法分派
goo.Doo(b); // Foo.Doo(A)

//Doo不是虚方法,但dgoo是dynamic,b不是dynamic,dgoo参与方法分派
dgoo.Doo(b); // Goo.Doo(A)

Console.ReadKey();
}
}
}